#ifndef HASH_H_
#define HASH_H_

class Hash {

public:
	virtual uint64_t hash(Dbt& dbtkey) = 0;
};

class Fnv1Hash32 : public Hash {

private:
	
    uint64_t BITS_MASK; 
    uint64_t FNV_BASIS;
    uint64_t FNV_PRIME;
    
public:
	Fnv1Hash32() {
	
		BITS_MASK = 0xffffffffL;
		FNV_BASIS = 0x811c9dc5L;
		FNV_PRIME = (1 << 24) + 0x193;
	}   

    inline uint64_t hash(Dbt& dbtkey) {
    
        const uint8_t* buf = reinterpret_cast<const uint8_t *>(dbtkey.data());

        uint64_t hash = FNV_BASIS;
        for(int i = 0; i < dbtkey.getDatalen(); i++) {
        
            hash *= FNV_PRIME;
            hash &= BITS_MASK;
            hash ^= 0xFF & buf[i];
        }
        
        return (hash == 0) ? BITS_MASK : hash;
    }
};


class MurmurHash : public Hash {
public:

    uint64_t hash(Dbt& dbtkey) {
        
        const char* key = dbtkey.data();
        uint32_t len = dbtkey.getDatalen();
        const uint64_t m = 0xc6a4a7935bd1e995;
        const int r = 47;
        uint32_t seed = 0x9747b28c;

        uint64_t h = seed ^ (len * m);

        const uint64_t * data = (const uint64_t *)key;
        const uint64_t * end = data + (len/8);

        while(data != end) {
        
            uint64_t k = *data++;

            k *= m; 
            k ^= k >> r; 
            k *= m; 

            h ^= k;
            h *= m; 
        }

        const unsigned char * data2 = (const unsigned char*)data;

        switch(len & 7) {
        
            case 7: h ^= uint64_t(data2[6]) << 48;
            case 6: h ^= uint64_t(data2[5]) << 40;
            case 5: h ^= uint64_t(data2[4]) << 32;
            case 4: h ^= uint64_t(data2[3]) << 24;
            case 3: h ^= uint64_t(data2[2]) << 16;
            case 2: h ^= uint64_t(data2[1]) << 8;
            case 1: h ^= uint64_t(data2[0]);
                    h *= m;
        };

        h ^= h >> r;
        h *= m;
        h ^= h >> r;

        return h;
    } 



};

#endif
